﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using System.IO;
using Tesla.Graphics;
using Tesla.Util;

namespace Tesla.Content.Loaders.Image {
    public sealed class DDSLoader : ResourceLoader<Texture> {

        public DDSLoader() 
            : base(".dds") {

        }

        public override Texture Load(IResource resource, LoaderParameters parameters) {

            Stream stream = resource.OpenStream();
            BinaryReader input = new BinaryReader(stream);

            DDSImageInfo image = ReadHeaderInfo(input);

            SurfaceFormat format = DetermineFormat(image);
            TextureDimensions dimensions = DetermineTextureDimensions(image);
            switch(dimensions) {
                case TextureDimensions.Two:
                    Texture2D tex2d = LoadTexture2D(image, input, format);
                    tex2d.Name = resource.Name;
                    input.Close();
                    return tex2d;
                case TextureDimensions.Three:
                    Texture3D tex3d = LoadTexture3D(image, input, format);
                    tex3d.Name = resource.Name;
                    input.Close();
                    return tex3d;
                case TextureDimensions.Cube:
                    TextureCube texcube = LoadTextureCube(image, input, format);
                    texcube.Name = resource.Name;
                    input.Close();
                    return texcube;
                default:
                    input.Close();
                    throw new InvalidOperationException("Error loading DDS file");
            }
        }

        private static int CalculateMipMapSize(bool compressed, int width, int height, int bpp, int level) {
            int size = 0;
            if(compressed) {
               //size = System.Math.Max(1, width / 4) * System.Math.Max(1, height / 4) * bpp;
                size = ((width + 3) / 4) * ((height + 3) / 4) * bpp;
            } else {
                size = width * height * bpp;
            }
            size = ((size + 3) / 4) * 4;
            return size;
        }

        private static Texture2D LoadTexture2D(DDSImageInfo image, BinaryReader input, SurfaceFormat format) {
            DDSHeader header = image.Header;

            int width = header.DwWidth;
            int height = header.DwHeight;
            int flags = header.ImageFormat.DwFlags;
            bool compressedFormat = IsSet(flags, (int) DDSPixelFlags.FourCC);
            int[] mipMapSizes = new int[header.DwMipMapCount];
            Texture2D tex = new Texture2D(width, height, mipMapSizes.Length > 1, format);

            if(compressedFormat) {
                int num = 16;
                if(format == SurfaceFormat.DXT1) {
                    num = 8;
                }
                int mipLevel = 0;
                while(mipLevel < header.DwMipMapCount) {
                    byte[] data = input.ReadBytes(CalculateMipMapSize(true, width, height, num, mipLevel));
                    tex.SetData<byte>(data, mipLevel, null, 0, data.Length);

                    width = System.Math.Max(width / 2, 1);
                    height = System.Math.Max(height / 2, 1);
                    mipLevel++;
                }
            }
            return tex;
        }

        private static Texture3D LoadTexture3D(DDSImageInfo image, BinaryReader input, SurfaceFormat format) {
            throw new NotImplementedException();
        }

        private static TextureCube LoadTextureCube(DDSImageInfo image, BinaryReader input, SurfaceFormat format) {
            DDSHeader header = image.Header;

            int width = header.DwWidth;
            int height = header.DwHeight;
            int flags = header.ImageFormat.DwFlags;

            bool compressedFormat = IsSet(flags, (int) DDSPixelFlags.FourCC);
            int[] mipMapSizes = new int[header.DwMipMapCount];
            TextureCube tex = new TextureCube(width, mipMapSizes.Length > 1, format);

            if(compressedFormat) {
                int num = 16;
                if(format == SurfaceFormat.DXT1) {
                    num = 8;
                }
                for(int face = 0; face < 6; face++) {
                    int mipLevel = 0;
                    while(mipLevel < header.DwMipMapCount) {
                        byte[] data = input.ReadBytes(CalculateMipMapSize(true, width, height, num, mipLevel));
                        tex.SetData<byte>((CubeMapFace) face, data, mipLevel, null, 0, data.Length);

                        width = System.Math.Max(width / 2, 1);
                        height = System.Math.Max(height / 2, 1);
                        mipLevel++;
                    } 

                    width = header.DwWidth;
                    height = header.DwHeight;
                }
            }
            return tex;
        }

        private static TextureDimensions DetermineTextureDimensions(DDSImageInfo image) {
            DDSHeader header = image.Header;
            DDSHeader10 header10 = image.Header10;

            if(IsSet(header.DwCaps2, (int) DDSCaps2.CubeMap)) {
                return TextureDimensions.Cube;
            } else if(IsSet(header.DwCaps2, (int) DDSCaps2.Volume)) {
                return TextureDimensions.Three;
            } else {
                return TextureDimensions.Two;
            }
        }

        private static SurfaceFormat DetermineFormat(DDSImageInfo image) {
            DDSHeader header = image.Header;
            DDSHeader10 header10 = image.Header10;

            int flags = header.ImageFormat.DwFlags;
            bool compressedFormat = IsSet(flags, (int) DDSPixelFlags.FourCC);
            bool rgb = IsSet(flags, (int) DDSPixelFlags.RGB);
            bool alphaPixels = IsSet(flags, (int) DDSPixelFlags.AlphaPixels);
            bool alpha = IsSet(flags, (int) DDSPixelFlags.Alpha);
            bool luminance = IsSet(flags, (int) DDSPixelFlags.Luminance);

            if(compressedFormat) {
                int fourCC = header.ImageFormat.DwFourCC;
                if(fourCC == GetInt("DXT1")) {
                    return SurfaceFormat.DXT1;
                } else if(fourCC == GetInt("DXT3")) {
                    return SurfaceFormat.DXT3;
                } else if(fourCC == GetInt("DXT5")) {
                    return SurfaceFormat.DXT5;
                } 
            } else {
                if(rgb) {
                    int bpp = header.ImageFormat.DwRGBBitCount;
                    switch(bpp) {
                        case 32:
                            return SurfaceFormat.Color;
                    }
                } else if(alphaPixels && alpha && (header.ImageFormat.DwRGBBitCount == 8)) {
                    return SurfaceFormat.Alpha8;
                } 
            }

            throw new InvalidDataException("Unsupported DDS format.");
        }

        private static bool IsSet(int value, int mask) {
            if((value & mask) == mask) {
                return true;
            } else {
                return false;
            }
        }

        private static int GetInt(String s) {
            char[] chars = s.ToCharArray();
            byte[] bytes = new byte[chars.Length];
            for(int i = 0; i < chars.Length; i++) {
                bytes[i] = Convert.ToByte(chars[i]);
            }
            return GetInt(bytes);
        }

        private static int GetInt(byte[] bytes) {
            int value = 0;
            value |= (bytes[0] & 0xff) << 0;
            if(bytes.Length > 1) {
                value |= (bytes[1] & 0xff) << 8;
            }
            if(bytes.Length > 2) {
                value |= (bytes[2] & 0xff) << 16;
            }
            if(bytes.Length > 3) {
                value |= (bytes[3] & 0xff) << 24;
            }
            return value;
        }

        private static DDSImageInfo ReadHeaderInfo(BinaryReader input) {
            int dwMagic = input.ReadInt32();
            if(dwMagic != GetInt("DDS ")) {
                throw new InvalidOperationException("Not a DDS file");
            }

            DDSImageInfo image = new DDSImageInfo();

            image.Header = ReadHeader(input);
            if(image.Header.ImageFormat.DwFourCC == GetInt("DX10")) {
                image.Header10 = ReadHeader10(input);
            } else {
                image.Header10.Exists = false;
            }
            return image;
        }

        private static DDSHeader ReadHeader(BinaryReader input) {
            DDSHeader header = new DDSHeader();
            header.DwSize = input.ReadInt32();
            if(header.DwSize != 124) {
                throw new InvalidOperationException("Invalid dds header size.");
            }
            header.DwFlags = input.ReadInt32();
            header.DwHeight = input.ReadInt32();
            header.DwWidth = input.ReadInt32();
            header.DwLinearSize = input.ReadInt32();
            header.DwDepth = input.ReadInt32();
            header.DwMipMapCount = input.ReadInt32();
            header.DwAlphaBitDepth = input.ReadInt32();
            header.DwReserved1 = new int[10];
            for(int i = 0; i < 10; i++) {
                header.DwReserved1[i] = input.ReadInt32();
            }
            header.ImageFormat = ReadImageFormat(input);
            header.DwCaps = input.ReadInt32();
            header.DwCaps2 = input.ReadInt32();
            header.DwCaps3 = input.ReadInt32();
            header.DwCaps4 = input.ReadInt32();
            header.DwTextureStage = input.ReadInt32();

            int mipMaps = 1 + (int) System.Math.Ceiling(System.Math.Log(System.Math.Max(header.DwHeight, header.DwWidth)) / System.Math.Log(2));
            DDSCaps cap = (DDSCaps) header.DwCaps;
            if((cap & DDSCaps.MipMap) == DDSCaps.MipMap) {
                DDSFlags flag = (DDSFlags) header.DwFlags;
                if((flag & DDSFlags.MipCount) != DDSFlags.MipCount) {
                    header.DwMipMapCount = mipMaps;
                }
            } else {
                header.DwMipMapCount = 1;
            }
            
            return header;
        }

        private static DDSHeader10 ReadHeader10(BinaryReader input) {
            DDSHeader10 header10 = new DDSHeader10();
            header10.Exists = true;
            header10.Format = ReadDXGIFormat(input);
            header10.Dimension = ReadResourceDimension(input);
            header10.MiscFlag = input.ReadInt32();
            header10.ArraySize = input.ReadInt32();
            header10.Reserved = input.ReadInt32();
            return header10;
        }

        private static DXGIFormat ReadDXGIFormat(BinaryReader input) {
            int dxgi = input.ReadInt32();
            foreach(DXGIFormat format in MemoryHelper.GetEnumValues(new DXGIFormat())) {
                if(dxgi == (int) format) {
                    return format;
                }
            }
            return DXGIFormat.Unknown;
        }

        private static D3D10ResourceDimension ReadResourceDimension(BinaryReader input) {
            int dxgi = input.ReadInt32();
            foreach(D3D10ResourceDimension format in MemoryHelper.GetEnumValues(new D3D10ResourceDimension())) {
                if(dxgi == (int) format) {
                    return format;
                }
            }
            return D3D10ResourceDimension.Unknown;
        }

        private static DDSImageFormat ReadImageFormat(BinaryReader input) {
            DDSImageFormat iformat = new DDSImageFormat();
            iformat.DwSize = input.ReadInt32();
            if(iformat.DwSize != 32) {
                throw new InvalidOperationException("Invalid image format size.");
            }
            iformat.DwFlags = input.ReadInt32();
            iformat.DwFourCC = input.ReadInt32();
            iformat.DwRGBBitCount = input.ReadInt32();
            iformat.DwRBitMask = input.ReadInt32();
            iformat.DwGBitMask = input.ReadInt32();
            iformat.DwBBitMask = input.ReadInt32();
            iformat.DwABitMask = input.ReadInt32();
            return iformat;
        }

    }

    struct DDSHeader {
        public int DwSize;
        public int DwFlags;
        public int DwHeight;
        public int DwWidth;
        public int DwLinearSize;
        public int DwDepth;
        public int DwMipMapCount;
        public int DwAlphaBitDepth;
        public int[] DwReserved1;
        public DDSImageFormat ImageFormat;
        public int DwCaps;
        public int DwCaps2;
        public int DwCaps3;
        public int DwCaps4;
        public int DwTextureStage;
    }

    struct DDSHeader10 {
        public bool Exists;
        public DXGIFormat Format;
        public D3D10ResourceDimension Dimension;
        public int MiscFlag;
        public int ArraySize;
        public int Reserved;
    }

    struct DDSImageInfo {
        public DDSHeader Header;
        public DDSHeader10 Header10;
    }

    struct DDSImageFormat {
        public int DwSize;
        public int DwFlags;
        public int DwFourCC;
        public int DwRGBBitCount;
        public int DwRBitMask;
        public int DwGBitMask;
        public int DwBBitMask;
        public int DwABitMask;
    }

    enum D3D10ResourceDimension {
        Unknown = 0,
        Buffer = 1,
        Texture1D = 2,
        Texture2D = 3,
        Texture3D = 4
    }

    [Flags]
    enum DDSFlags {
        Caps = 0x1,
        Height = 0x2,
        Width = 0x4,
        Pitch = 0x8,
        Format = 0x1000,
        MipCount = 0x20000,
        LinearSize = 0x80000,
        Depth = 0x800000
    }

    [Flags]
    enum DDSPixelFlags {
        AlphaPixels = 0x1,
        Alpha = 0x2,
        FourCC = 0x4,
        RGB = 0x40,
        YUV = 0x200,
        Luminance = 0x20000
    }

    [Flags]
    enum DDSCaps {
        Complex = 0x8,
        MipMap = 0x400000,
        Texture = 0x1000
    }

    enum DDSCaps2 {
        CubeMap = 0x200,
        CubeMapPositiveX = 0x400,
        CubeMapNegativeX = 0x800,
        CubeMapPositiveY = 0x1000,
        CubeMapNegativeY = 0x2000,
        CubeMapPositiveZ = 0x4000,
        CubeMapNegativeZ = 0x8000,
        Volume = 0x200000
    }

    [Flags]
    enum D3D10ResourceMisc {
        GenerateMips = 0x1,
        Shared = 0x2,
        TextureCube = 0x4,
        SharedKeyedMutex = 0x10,
        GDICompatible = 0x20
    }

    enum DXGIFormat {
        Unknown = 0,
        R32G32B32A32_Typeless = 1,
        R32G32B32A32_Float = 2,
        R32G32B32A32_Uint = 3,
        R32G32B32A32_Sint = 4,
        R32G32B32_Typeless = 5,
        R32G32B32_Float = 6,
        R32G32B32_Uint = 7,
        R32G32B32_Sint = 8,
        R16G16B16A16_Typeless = 9,
        R16G16B16A16_Float = 10,
        R16G16B16A16_Unorm = 11,
        R16G16B16A16_Uint = 12,
        R16G16B16A16_Snorm = 13,
        R16G16B16A16_Sint = 14,
        R32G32_Typeless = 15,
        R32G32_Float = 16,
        R32G32_Uint = 17,
        R32G32_Sint = 18,
        R32G8X24_Typeless = 19,
        D32_Float_S8X24_Uint = 20,
        D32_Float_X8X24_Typeless = 21,
        X32_Typeless_G8X24_Uint = 22,
        R10G10B10A2_Typeless = 23,
        R10G10B10A2_Unorm = 24,
        R10G10B10A2_Uint = 25,
        R11G11B10_Float = 26,
        R8G8B8A8_Typeless = 27,
        R8G8B8A8_Unorm = 28,
        R8G8B8A8_Unorm_SRGB = 29,
        R8G8B8A8_Uint = 30,
        R8G8B8A8_Snorm = 31,
        R8G8B8A8_Sint = 32,
        R16G16_Typeless = 33,
        R16G16_Float = 34,
        R16G16_Unorm = 35,
        R16G16_Uint = 36,
        R16G16_Snorm = 37,
        R16G16_Sint = 38,
        R32_Typeless = 39,
        D32_Float = 40,
        R32_Float = 41,
        R32_Uint = 32,
        R32_Sint = 43,
        R24G8_Typeless = 44,
        D24_Unorm_S8_Uint = 45,
        R24_Unorm_X8_Typeless = 46,
        X24_Typeless_G8_Uint = 47,
        R8G8_Typeless = 48,
        R8G8_Unorm = 49,
        R8G8_Uint = 50,
        R8G8_Snorm = 51,
        R8G8_Sint = 52,
        R16_Typeless = 53,
        R16_Float = 54,
        D16_Unorm = 55,
        R16_Unorm = 56,
        R16_Uint = 57,
        R16_Snorm = 58,
        R16_Sint = 59,
        R8_Typeless = 60,
        R8_Unorm = 61,
        R8_Uint = 62,
        R8_Snorm = 63,
        R8_Sint = 64,
        A8_Unorm = 65,
        R1_Unorm = 66, 
        R9G9B9E5_SharedExp = 67,
        R8G8_B8G8_Unorm = 68,
        G8R8_G8B8_Unorm = 69,
        BC1_Typeless = 70,
        BC1_Unorm = 71,
        BC1_Unorm_SRGB = 72,
        BC2_Typeless = 73,
        BC2_Unorm = 74,
        BC2_Unorm_SRGB = 75,
        BC3_Typeless = 76,
        BC3_Unorm = 77,
        BC3_Unorm_SRGB = 78,
        BC4_Typeless = 79,
        BC4_Unorm = 80,
        BC4_Snorm = 81,
        BC5_Typeless = 82,
        BC5_Unorm = 83,
        BC5_Snorm = 84,
        B5G6R5_Unorm = 85,
        B5G5R5A1_Unorm = 86,
        B8G8R8A8_Unorm = 87,
        B8G8R8X8_Unorm = 88,
        R10G10B10_XR_Bias_A2_Unorm = 89,
        B8G8R8A8_Typeless = 90,
        B8G8R8A8_Unorm_SRGB = 91,
        B8G8R8X8_Typeless = 92,
        B8G8R8X8_Unorm_SRGB = 93,
        BC6H_Typeless = 94,
        BC6H_UF16 = 95,
        BC6H_SF16 = 96,
        BC7_Typeless = 97,
        BC7_Unorm = 98,
        BC7_Unorm_SRGB = 99
    }
}
